package cn.itcast.dtx.tccdemo.bank1.service.impl;

import cn.itcast.dtx.tccdemo.bank1.client.Bank2Client;
import cn.itcast.dtx.tccdemo.bank1.dao.AccountInfoDao;
import cn.itcast.dtx.tccdemo.bank1.service.AccountInfoService;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hmily.annotation.Hmily;
import org.dromara.hmily.core.concurrent.threadlocal.HmilyTransactionContextLocal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
public class AccountInfoServiceImpl implements AccountInfoService {
    @Autowired
    AccountInfoDao accountInfoDao;
    @Autowired
    Bank2Client bank2Client;

    /**
     * 更新账户余额，我们要做这四件事
     * 1、try幂等校验
     * 2、try悬挂处理
     * 3、检查余额是够，够则执行扣减金额,否则直接抛异常
     * 4、添加try记录，方便进行幂等性判断
     *
     * @param accountNo
     * @param amount
     */
    //只要是 @Hmily注解标注的方法就是try方法，用这个框架只需要暴露try方法即可。而且三个方法的参数必须要一样。
    @Hmily(confirmMethod = "confirmUpdateAccountBlance", cancelMethod = "cancelUpdateAccountBlance")
    @Transactional
    @Override
    public void updateAccountBlance(String accountNo, Double amount) {
        //获取全局事物id localTradeNo
        final String localTradeNo = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("bank1 try begin.. xid : {}", localTradeNo);
        log.info("updateAccountBlance开始执行，更新账户【{}】减少余额【{}】，事物id:{}",accountNo,amount,localTradeNo);
        //try幂等校验 因为 try  confirm  cancel 会进行重试，要保证幂等性
        //幂等判断，判断local_try_log表中是否有try日志记录，如果有则表示try执行了，那不再执行。
        if (accountInfoDao.isExistTry(localTradeNo) > 0) {
            log.info("更新账户余额已经执行try，无需重复执行!localTradeNo:{}", localTradeNo);
            return;
        }
        //try悬挂处理，就是 cancel 和  confirm 存在就不能执行try
        if (accountInfoDao.isExistCancel(localTradeNo) > 0 || accountInfoDao.isExistConfirm(localTradeNo) > 0) {
            log.info("更新账户余额已经执行悬挂处理confirm或cancel，事物id:{}", localTradeNo);
            return;
        }
        //检查余额是够扣减金额
        if (accountInfoDao.subtractAccountBalance(accountNo, amount) != 1) {
            log.info("账户【{}】扣减余额【{}】失败，事物id:{}", accountNo, amount, localTradeNo);
            throw new RuntimeException("账户【{" + accountNo + "}】扣减余额【{" + amount + "}】失败，事物id" + localTradeNo);
        }
        //增加本地事务try成功记录，用于幂等性控制标识
        accountInfoDao.addTry(localTradeNo);
        //远程调用bank2，注意远程调用出错的话，异常要抛出去，要给框架捕获到
        if (!bank2Client.transfer(amount)) {
            log.info("远程调用转账接口失败，转账金额【{}】，事物id：{}", accountNo, localTradeNo);
            throw new RuntimeException("远程调用转账接口失败，转账金额【" + accountNo + "】，事物id：" + localTradeNo);
        }
    }

    /**
     * 确认阶段 进行数据更新操作
     *
     * @param accountNo
     * @param amount
     */
    public void confirmUpdateAccountBlance(String accountNo, Double amount) {
        final String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("confirmUpdateAccountBlance确认阶段，全局事物id:{}", transId);
    }

    /**
     * 取消阶段 进行数据回滚操作
     *
     * 1、cancel幂等校验
     * 2、cancel空回滚
     * 3、增加余额
     * * @param amount
     */
    @Transactional
    public void cancelUpdateAccountBlance(String accountNo, Double amount) {
        final String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("cancelUpdateAccountBlance取消阶段开始执行，全局事物id:{}", transId);
        //判断是否空回滚
        if (accountInfoDao.isExistTry(transId) == 0) {
            log.info("try阶段没有执行，无需进行cancelUpdateAccountBlance，事物id:{}", transId);
            return;
        }
        //幂等性校验，已经执行过了，什么也不用做
        if (accountInfoDao.isExistCancel(transId) > 0) {
            log.info("cancelUpdateAccountBlance已经执行，无需重复执行，事物id:{}", transId);
            return;
        }
        //执行账户金额退款
        final int result = accountInfoDao.addAccountBalance(accountNo, amount);
        log.info("cancelUpdateAccountBlance执行取消阶段执行addAccountBalance结果：【{}】，账户【{}】，回退金额【{}】，事物id:{}"
                , result, accountNo, amount,transId);
        //添加cancel日志，用于幂等性判断
        accountInfoDao.addCancel(transId);
        log.info("cancelUpdateAccountBlance取消阶段执行结束，事物id:{}", transId);
    }
}
